Monitoring Server Configuration

Monitoring Server Configuration

This describes the process of building a new monitoring server from scratch. This was modeled on the HamWAN server running in our Fremont site, but build on a new vintage VM and with more modern software versions. Rather than Cacti, we use Zabbix as the monitoring engine.

Added Debian trixie net-install ISO to proxmox server

Create a VM, and install Debian trixie

Boot and configure as a minimal server using the graphical interface

apt install jq locate mg mtr postfix pylint python3-virtualenv redis-server redis-tools rsyslog strace sudo tcpdump
# update the locate database
updatedb
/sbin/groupadd hamadmin
add /etc/sudoers.d/hamadmin (copied from monitoring.hamwan.net)

Change /etc/ssh/sshd_config to move the SSH server to port 222, then restart sshd with systemctl restart sshd.

My initial account had to be lower case (so kd7dk). I then fixed that to my standard HamWAN KD7DK account:

  1. useradd -G hamadmin -m -c “Doug Kingston” KD7DK
  2. cd /home/KD7DK
  3. cp -pr /home/kd7dk/.ssh /home/KD7DK/.ssh
  4. chown -R KD7DK:KD7DK /home/KD7DK/.ssh
  5. chsh -s /bin/bash KD7DK
  6. userdel -r kd7dk

Note: at this point I believe our ansible automation is capable of creating all the other netop accounts.

Add Management LAN interface

The monitoring server sits on two networks and must never join them:

We use 2 routing domains (the default VRF and mgmt) to enforce separation. Why? One OSPF instance has one link-state database, so the host forms adjacencies on both networks and floods LSAs between them, and the two networks merge into one routing domain. Filters only control what is installed or redistributed, not what is flooded.

The design is:

Order of operations

The steps depend on each other. Do them in this order:

  1. Create the mgmt VRF and move ens19 into it (this section)
  2. Set the VRF sysctls (this section)
  3. Allow OSPF on the mgmt interface in the host firewall (next section)
  4. Install and configure FRR (next section; it fails if the VRF does not exist yet)
  5. After Docker is installed: pin the Zabbix frontend subnet, fix the MariaDB socket mount, add the raw-table and transit guard rules, and order Docker after MariaDB (see Zabbix and the management network)
  6. Reboot and run the verification checklist at the end

Moving ens19 into a VRF flushes its addresses and routes, so do step 1 from the console, not over an SSH session that arrives via the management LAN.

1. Create the VRF and plumb ens19

This host uses classic ifupdown, which has no native vrf keyword (that is ifupdown2 syntax). The VRF is created from hooks in the ens19 stanza of /etc/network/interfaces. The address is added after pre-up, so it ends up inside the VRF.

# The management network interface
auto ens19
iface ens19 inet static
    address 10.44.4.8/24
    pre-up    ip link add mgmt type vrf table 200 2>/dev/null || true
    pre-up    ip link set dev mgmt up
    pre-up    ip link set dev ens19 master mgmt
    # only if mgmt needs a static default (omit if learned via OSPF):
    #up       ip route add vrf mgmt default via <mgmt-gw> dev ens19
    # Policy routing: let host processes reach management hosts (see below).
    up        ip rule add to 10.44.0.0/16 lookup 200 pref 100
    up        ip rule add to 172.16.241.0/24 lookup main pref 90
    # Loose reverse-path filtering; strict mode breaks VRF/NAT return paths.
    up        sysctl -w net.ipv4.conf.ens19.rp_filter=2
    up        sysctl -w net.ipv4.conf.mgmt.rp_filter=2
    post-down ip rule del to 10.44.0.0/16 lookup 200 pref 100 || true
    post-down ip rule del to 172.16.241.0/24 lookup main pref 90 || true
    post-down ip link set dev ens19 nomaster || true
    post-down ip link del mgmt || true

Do not use the gateway keyword here. It would place a default route in the main table. Management routes are learned by OSPF inside the VRF.

What the two ip rule lines do:

Rule Purpose
pref 100: to 10.44.0.0/16 lookup 200 Processes in the default VRF (Zabbix pollers, the agent, ping) reach management hosts via the mgmt table. Without it, management prefixes exist only in table 200 and these processes can only use the public network.
pref 90: to 172.16.241.0/24 lookup main Return path for Zabbix containers. Replies from management hosts arrive on the VRF and are addressed to the container (after NAT is undone), so the lookup for the Docker frontend subnet must use the main table, where the Docker bridge route lives. The subnet must match the pinned frontend subnet (see the Zabbix section).

2. VRF sysctls

cat > /etc/sysctl.d/90-vrf.conf <<'EOF'
# Sockets in the default VRF accept traffic arriving on the mgmt VRF
net.ipv4.tcp_l3mdev_accept = 1
net.ipv4.udp_l3mdev_accept = 1
# Docker (Zabbix containers) needs forwarding
net.ipv4.ip_forward = 1
EOF
sysctl --system

Do not set net.ipv4.ip_forward = 0 anywhere in /etc/sysctl.d/. Docker requires it. Check for conflicting entries with:

grep -rn -E 'rp_filter|ip_forward|l3mdev' /etc/sysctl.conf /etc/sysctl.d/ /usr/lib/sysctl.d/

The per-interface rp_filter settings are in the ens19 stanza above, because those interfaces do not exist when sysctl.d is read at boot.

3. Verify the VRF before going further

ip -br link show master mgmt        # should list ens19
ip -br addr show ens19              # 10.44.4.8/24
ip rule                             # pref 90 and pref 100 rules, then l3mdev at 1000
ip vrf exec mgmt ping -c2 <mgmt-router>

A plain ping 10.44.4.x from the default VRF is not a valid test of the VRF itself. Use ip vrf exec mgmt.

Install FRR (OSPF Routing)

The public network and the management network each get their own OSPF instance. The public instance runs in the default VRF; the management instance runs in the mgmt VRF. A single ospfd handles both, so the older approach of running two ospfd processes with instance numbers is no longer used.

Host firewall: allow OSPF on the management interface

For traffic received on a VRF slave interface, netfilter’s INPUT chain sees the input interface as the VRF device (mgmt), not ens19. Any existing rule that names ens19 must be changed to mgmt. Without this, no OSPF adjacency forms on the management network, even though everything else looks right.

iptables -I INPUT -i mgmt -p 89 -j ACCEPT

OSPF is IP protocol 89 (multicast 224.0.0.5 and 224.0.0.6). Keep the rule scoped to mgmt so the public side does not accept OSPF from the wrong place. Persist it with whichever mechanism the server uses for its own host firewall rules (for example netfilter-persistent), and do not save Docker’s chains into the persistent ruleset.

Install and configure

apt install frr

In /etc/frr/daemons:

zebra=yes
ospfd=yes

Make sure FRR starts after the network (and therefore the VRF) exists. On Debian this is normally already true, but check:

systemctl show frr -p After | tr ' ' '\n' | grep -E 'network|networking'

If networking.service is not listed, add an override. Be sure to read the comments in the prototype provide:

systemctl edit frr
[Unit]
After=networking.service
Wants=networking.service

/etc/frr/frr.conf:

frr defaults traditional
hostname monitoring.ziply.hamwan.net
log file /var/log/frr/frr.log
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config

password <PASSWORD>
enable password <PASSWORD>

vrf mgmt
exit-vrf

! ---- public network (default VRF) ----
interface lo
 ip ospf area 0
 ip ospf passive
exit

interface ens18
 ip ospf area 0
 ip ospf authentication message-digest
 ip ospf message-digest-key 1 md5 <OSPF_PASSWORD>
 ip ospf priority 0
exit

router ospf
 ospf router-id 44.25.67.58
 max-metric router-lsa administrative
 area 0 authentication message-digest
exit

! ---- management network (mgmt VRF, separate LSDB) ----
interface ens19 vrf mgmt
 ip ospf area 0
 ip ospf authentication null
 ip ospf priority 0
exit

router ospf vrf mgmt
 ospf router-id 10.44.4.8
 max-metric router-lsa administrative
exit

line vty
!

Notes:

systemctl enable --now frr

Verify routing

vtysh -c 'show vrf'
vtysh -c 'show ip ospf neighbor'                # public neighbours only
vtysh -c 'show ip ospf vrf mgmt neighbor'       # management neighbours only
vtysh -c 'show ip route ospf'                   # public routes only
vtysh -c 'show ip route vrf mgmt ospf'          # management routes only
ip route show vrf mgmt                          # kernel view of table 200
ip route get 10.44.4.1                          # should leave via ens19 (policy rule)

In show ip route, O>* means an OSPF route that is selected and installed in the kernel.

After a change, neighbours may take a few minutes to age out old LSAs.

Zabbix Monitoring System

Install Zabbix

Install git.

apt install git

install Docker according to https://docs.docker.com/engine/install/debian/ from https://github.com/zabbix/zabbix-docker/tree/7.2 Follow model docker-compose_v3_ubuntu_mysql_latest.yaml

Add the following to /etc/fstab: /dev/mapper/monitoring–data–vg-data /data ext4 defaults 0 2 followed by: systemctl daemon-reload

If necessary, mount /data.

\# install mysql (via mariadb fork)
apt install mariadb-server
systemctl stop mariadb
mkdir /data/mysql
chown mysql:mysql /data/mysql

Edit /etc/mysql/mariadb.conf.d/50-server.cnf datadir = /data/mysql innodb_buffer_pool_size = 8G

Add empty database to myql, grant access to ‘zabbix’ with a password.

mysql << DONE
create database zabbix;
grant all privileges on \*.\* to 'zabbix'@'localhost' identified by 'some-password';
DONE

Create a user to run zabbix containers and install the agent:

useradd -m -c "Zabbix Monitoring" zabbix

# in the container zabbix-server
chmod u+s /usr/bin/fping

# Install zabbix-agent Debian package (here and on other Linux servers)
apt install zabbix-agent

# then configure /etc/zabbix/zabbix\_agentd.conf
Server=172.16.241.0/24
ServerActive=172.16.241.3:10051
Hostname=monitoring.ziply.hamwan.net

This uses the zabbix container addresses.

On any other repeater, this would changes would look likeL

Server=44.25.67.58
ServerActive=44.25.67.58:10051
Hostname=monitoring.ziply.hamwan.net

Server can be an address, CIDR range, or list of either. See the manual for more details.

Disable unnecessary discovery in Mikrotik Template (CapsMAN, LTE)

Zabbix and the management network

The Zabbix server runs in a container on the zabbix-docker_frontend network, which carries its default route. Its polls to management hosts are routed through the mgmt VRF by the policy rule from the networking section and are NATed (masqueraded) to the host’s management address, 10.44.4.8. Several things must be in place for this to work and to survive reboots.

1. Pin the frontend subnet. The policy rule pref 90 in /etc/network/interfaces names the Docker frontend subnet. Docker assigns subnets automatically and may choose a different one if the network is recreated, so pin it in the compose file (merge into the existing frontend: network definition):

networks:
  frontend:
    ipam:
      config:
        - subnet: 172.16.241.0/24

To check which network a container uses for its default route, read /proc/net/route inside it. The default route’s gateway is printed as little-endian hex (01F110AC is 172.16.241.1).

docker exec zabbix-docker-zabbix-server-1 cat /proc/net/route

2. Do not bind-mount the MariaDB socket file. MariaDB runs on the host, with its data in /data/mysql. If a compose service has a short-syntax bind mount of the socket file, for example:

- /run/mysqld/mysqld.sock:/run/mysqld/mysqld.sock

Docker creates a missing source path as a directory. If Docker starts the containers before MariaDB, /run/mysqld/mysqld.sock becomes a directory and MariaDB then fails to start with Bind on unix socket: Address already in use. Mount the directory instead, which always exists:

- /run/mysqld:/run/mysqld

If a directory has already been created at that path, stop the containers, remove it, and start MariaDB first:

docker compose stop
rmdir /run/mysqld/mysqld.sock
systemctl start mariadb
ls -l /run/mysqld/mysqld.sock     # must start with "s"

3. Start Docker after MariaDB. The directory mount above helps, but ordering Docker after MariaDB also removes the race:

systemctl edit docker
# [Unit]
# After=mariadb.service
# Wants=mariadb.service

4. Allow replies from the management network through Docker’s anti-spoofing rule. Recent Docker versions add rules like the following in the raw table for each container:

-A PREROUTING -d 172.16.241.2/32 ! -i br-<id> -j DROP

Replies from management hosts come back through a VRF slave interface. The kernel handles them twice in PREROUTING, and on the second pass the reply is addressed to the container but arrives on mgmt, not the Docker bridge. Docker’s rule drops it. The symptom is that the reply is visible in tcpdump on ens19 and on mgmt, but never on the bridge, and the raw PREROUTING DROP counter for the container increases.

Add a narrow exception ahead of Docker’s rules:

iptables -t raw -I PREROUTING 1 -i mgmt -s 10.44.0.0/16 -d 172.16.241.0/24 -j ACCEPT

It matches only the mgmt device, only management-network sources, and only the Zabbix frontend subnet.

5. Block transit from the public side into the management network. Because the pref 100 rule applies to all route lookups on the host, packets forwarded by this host that arrive on the public interface and are addressed to 10.44.0.0/16 would also be sent into the mgmt table. Block that explicitly, so isolation does not depend on neighbours never routing management prefixes to this host:

iptables -I DOCKER-USER -i ens18 -d 10.44.0.0/16 -j DROP

To test it, add a route on a host on the public segment (ip route add 10.44.4.1/32 via <ens18-ip>) and ping 10.44.4.1. The DROP counter in iptables -L DOCKER-USER -v -n should increase, and nothing from that host should appear in tcpdump -ni ens19. Remove the test route afterwards. Container polls and the host’s own traffic are unaffected, because they do not arrive on ens18.

6. Make both rules persistent. Docker re-creates its raw rules when it restarts or recreates networks, so do not save these rules in a saved ruleset. Re-add them from a systemd unit ordered after Docker:

# /etc/systemd/system/mgmt-raw-accept.service
[Unit]
Description=Allow mgmt VRF replies to Docker frontend containers (raw PREROUTING, transit guard)
After=docker.service
Requires=docker.service
PartOf=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'iptables -t raw -C PREROUTING -i mgmt -s 10.44.0.0/16 -d 172.16.241.0/24 -j ACCEPT 2>/dev/null || iptables -t raw -I PREROUTING 1 -i mgmt -s 10.44.0.0/16 -d 172.16.241.0/24 -j ACCEPT'
ExecStart=/bin/sh -c 'iptables -C DOCKER-USER -i ens18 -d 10.44.0.0/16 -j DROP 2>/dev/null || iptables -I DOCKER-USER -i ens18 -d 10.44.0.0/16 -j DROP'

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now mgmt-raw-accept.service

PartOf=docker.service restarts the unit with Docker. If you recreate a compose network without restarting Docker (docker compose down then up), run systemctl restart mgmt-raw-accept afterwards.

7. Management hosts must accept the host’s address. Polls to management hosts now arrive from 10.44.4.8, not from the container address. SNMP ACLs and Zabbix agent Server= / ServerActive= settings on management-network devices must allow 10.44.4.8. The monitoring host’s own agent is unchanged (Server=172.16.241.0/24).

8. Verify from the container. Start the stack first (see Starting Zabbix under Zabbix Operations below), then verify from the container.

docker exec zabbix-docker-zabbix-server-1 ping -c2 10.44.4.1
docker exec zabbix-docker-zabbix-server-1 ping -c2 <host-in-10.44.32.8/29>
iptables -t raw -L PREROUTING -v -n | head -4     # counter on the mgmt ACCEPT rule increases

Then use “Execute now” on an item of a management-network host in the Zabbix UI and confirm it returns data.

Verification checklist (after a reboot)

Reboot once the whole stack is configured, and confirm everything comes back in the right order:

ip -br link show master mgmt                       # ens19 is in the mgmt VRF
ip rule                                            # pref 90, pref 100, l3mdev at 1000
ip route show vrf mgmt                             # connected route plus OSPF routes
sysctl net.ipv4.tcp_l3mdev_accept net.ipv4.udp_l3mdev_accept net.ipv4.ip_forward
sysctl net.ipv4.conf.ens19.rp_filter net.ipv4.conf.mgmt.rp_filter     # both 2
vtysh -c 'show ip ospf neighbor'                   # public neighbours only
vtysh -c 'show ip ospf vrf mgmt neighbor'          # management neighbours only
iptables -S INPUT | grep mgmt                      # OSPF accept rule present
iptables -S DOCKER-USER                            # Check for transit guard rule
iptables -t raw -S PREROUTING | head -3            # mgmt accept rule above Docker's drops
ls -l /run/mysqld/mysqld.sock                      # starts with "s"
docker compose ps                                  # Zabbix containers healthy

Isolation check: show ip ospf neighbor must list only public neighbours and show ip ospf vrf mgmt neighbor only management neighbours. Neither routing table should contain prefixes from the other network.

Troubleshooting the VRF setup

Symptom Cause Fix
No OSPF neighbours on the management network; tcpdump shows hellos arriving INPUT firewall rule matches ens19, but INPUT sees the VRF device mgmt Match -i mgmt
Host cannot ping its own management address or management hosts from the default VRF Management address and connected route live in table 200 only Use ip vrf exec mgmt ..., or rely on the pref 100 policy rule
Host processes reach public hosts but not management hosts Policy rule missing Check ip rule for the to 10.44.0.0/16 lookup 200 rule
Services listening in the default VRF refuse connections arriving on the management side VRF socket scoping net.ipv4.tcp_l3mdev_accept=1 (and udp_l3mdev_accept=1)
Container pings to management hosts time out; replies visible on ens19 and mgmt but not on the bridge Docker raw-table anti-spoofing rule drops replies arriving on mgmt Add the raw PREROUTING accept rule
Container pings still fail with replies reaching the bridge side Missing pref 90 rule, or NAT not applied Check ip rule and iptables -t nat -L POSTROUTING -v -n
MariaDB fails with Bind on unix socket: Address already in use and nothing else is running mysqld.sock is a directory created by a Docker bind mount Mount /run/mysqld as a directory, start MariaDB before Docker
interface ens19 vrf mgmt rejected by FRR FRR started before the VRF existed Create the VRF first, then systemctl restart frr
Zabbix cannot reach anything after Docker or iptables was reloaded Docker chains removed by a ruleset restore Restart Docker; keep Docker’s chains out of saved rulesets

Zabbix Operations

Starting Zabbix

cd ~zabbix/zabbix-docker
docker compose -f psdr.yaml --profile all up -d

Stopping Zabbix

cd ~zabbix/zabbix-docker
docker compose -f psdr.yaml --profile all down

Viewing Zabbix Logs (stdout/stderr)

docker logs -f _container_id_or_name_

Updating Zabbix parameters

If you want to change one of the Zabbix parameters that are specified in config files in /home/zabbix/zabbix-docker or a subdirectory like env_vars, you need to stop Zabbix and rebuild the effected containers.

cd ~zabbix/zabbix-docker
docker compose -f psdr.yaml --profile all down
docker compose -f psdr.yaml --profile all build --no-cache --parallel
docker compose -f psdr.yaml --profile all up -d

‘all’ can be replaced with the specific container if you know the impact of the update is limited but the safest option is to use all.

Zabbix Templates

Updated the Mikrotik by SNMP template to enhance dashboards and turn off some data collection (LTE, CAPSman)

Added Template for AirFiber by SNMP, and then created a varient with the PING and inventory update disabled/removed so that it can be combined tiw Network Generic Device by SNMP which provides important additional Triggers and Graphs not available in the AirFiber template as provided.

Upgrading Zabbix

Upgrades come in 2 pieces

  1. git pull of updated docker compose configuration files
  2. docker compose pull to get new container contents

Basic steps:

  1. review the release and upgrade notes in Zabbix documentation
  2. take a proxmox snapshot of the monitoring server
  3. if changing major release (e.g. 7.2 -> 7.4), then update /home/zabbinx/zabbix-docker/.env
  4. git checkout 7.2 (or whatever your major branch is)
  5. git pull # (opt for rebase if needed)
  6. git checkout PSDR-7.2
  7. git merge -b 7.2
  8. docker compose down <service names>
  9. docker compose up -d –build –force-recreate <service names>

To modify a parameter in env_vars/*:

  1. modify the relevant file (e.g. env_vars/.env_srv)
  2. docker compose -f psdr.yaml up -d
    (only rebuilds and restarts containers that need update)

For a minor upgrade (e.g. 7.4.10 -> 7.4.13):

  1. review the release and upgrade notes in Zabbix documentation
  2. take a proxmox snapshot of the monitoring server
  3. git checkout 7.4 (or whatever your major branch is)
  4. git pull
  5. git checkout PSDR-7.4.x
  6. git merge 7.4
  7. resolve any conflicts reported (edit files)
  8. git add updated files, then git commit to complete the merge operation
  9. docker compose down
    or
    docker -profile all down
  10. docker compose -f psdr.yaml pull
  11. docker compose -f psdr.yaml up –force-recreate
    or
    docker compose -f psdr.yaml –profile all up -d –build –force-recreate

For a major upgrade (e.g. 7.2 -> 7.4):

(Older instructions… This should probably be done more like the minor upgrade process.)

  1. review the release and upgrade notes in Zabbix documentation
  2. take a proxmox snapshot of the monitoring server
  3. git checkout 7.4 origin/7.4 # (or whatever your major branch is)
  4. git checkout -b PSDR-7.4 7.4
  5. git cherrypick -n 7.2..PSDR-7.2
  6. git merge -b 7.2
  7. if changing major release (e.g. 7.2 -> 7.4), then update /home/zabbinx/zabbix-docker/.env
  8. docker compose down <service names>
  9. docker compose up -d –build –force-recreate <service names>

rsyslog

Used configuration from Fremont largely unchanged. There is a key dependency on the Unfiltered.log file where the bulk of HamWAN infrastructure logs. Hacking attempts are fed into fail2ban from here. Configuration of client logging needs review and it had lots of hardcoded 44.24 addresses that were never updated. Need to consider what kind of separation we actually need.

Postfix

Basically out of the box. Will probably need to update to add authentication.

fail2ban

Used new fail2ban.conf as starting point and merged in HamWAN changes. Added new HamWAN files from Fremont with review.

Tom Hayward’s Fail2Ban docker containers

These provide a redis server and the long poll web servers for new bans. https://github.com/kd7lxl/blacklist-service

You also need to add this to 000-default-ssl.conf for apache:

        \<Location "/blacklist"\>
            ProxyPass "http://127.0.0.1:1234/"
        \</Location\>

You will need to enable mods proxy and proxy_http:

SSH Public ksys

Add /srv/www/keys and this stanza to 000-default-ssl.conf for apache:

        <Directory /srv/www/keys/>
            Options +Indexes +FollowSymLinks
            AllowOverride None
            Require all granted
        </Directory>

Setup backup of Mikrotik devices

Use the scripts in HamWAN Github repository.

TODO


Pending issues:

ICMP ping loss trigger is too sensitive. Needs a longer sample interval I believe.

redis is complaining at startup: WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add ‘vm.overcommit_memory = 1’ to /etc/sysctl.conf and then reboot or run the command ‘sysctl vm.overcommit_memory=1’ for this to take effect.

S2.CapitolPark.hamwan.net MikroTik: Interface wlan1(): Link down Caused because a sector that loses its last client connections will change OperStatus to down instead of dormant. This trigger may need to be revised for APs, or possibly deleted for APs.

zabbix-server-1 | 69:20250205:143203.601 cannot send list of active checks to “172.16.241.1”: host [monitoring] not found. This is a configuration issue I believe.

Figure out how to ensure all the proxmox servers have the seme inventory of install images.

Resolved issues:

Web GUI shows a valid certificate but complains about active content with certificate errors. Something like this posting: https://community.letsencrypt.org/t/chrome-69-0-3497-81-reports-active-content-with-certificate-errors/71545 Resolution: This was caused by cached javascript content from the prior self-signed certificate. Cleared the cached content and all was fine.

Ping health checks failing. Needed to add setuid bit to fping in the server container.

SNMP agent item “net.if.wireless.walk” on host “r1.capitolpark.hamwan.net” failed: first network error, wait for 15 seconds (and similar) Possibly: https://www.zabbix.com/forum/zabbix-troubleshooting-and-problems/483095-zabbix-7-snmp-timeouts Fixed this by increasing the timeout for SNMP queries (Administration > General > Timeouts). I changed it from 3s to 10s.

3 Mikrotik hosts are refusing to respond to SNMP get/getbulk In this case s2.indianola, r3.baldi and capitolpark.queenanne. This turned out to be a semi-known issue with SNMP and Mikrotik, and asymmetric routes. RouterOS would respond with the address of whichever interface had the best route back to the requester. If that was not the interface that the request was sent to, the requester would be unable to match it with the request it sent. Solution is to give all devices a stable address (on their loopback interface if they have more than one interface), make that the default address in the portal, and set src-address in /snmp to force all SNMP response to come from that address.

References:

Mikrotik templates (from Zabbix and third parties) https://www.zabbix.com/integrations/mikrotik

https://www.zabbix.com/documentation/current/en/manual/discovery/network_discovery